﻿module HostTracker.Azure.ConsoleManager
open System
open HostTracker.Azure.Management
open System.ServiceModel
open System.ServiceModel.Web
open HostTracker.Azure.Management.FSharpExtensions
open System.Text
open HostTracker.Azure.ProjectSettings

(* 1. Subscription info *)
let printMySubscription _ =     
    async {
        let! subscribtionOpt = 
            SubscriptionManagement.GetAsync SubscriptionManagement.MySubscriptionId
        match subscribtionOpt with
        | Success resp -> 
            printfn "Your subscribtion parameters: "
            printfn "Name = %A (Id = %A) (status = %A)" resp.data.Name resp.data.Id resp.data.StatusStr
            printfn "LiveId (account) = %A" resp.data.AccountAdminLiveId
            printfn "LiveId (service) = %A" resp.data.ServiceAdminLiveId
            printfn "Cores = %A (max = %A)" resp.data.CurrentCoreCount resp.data.MaxCoreCount
            printfn "HServices = %A (max = %A)" resp.data.CurrentHostedServices resp.data.MaxHostedServices
            printfn "SServices = %A (max = %A)" resp.data.CurrentStorageAccounts resp.data.MaxStorageAccounts
        | Fail resp ->
            printfn "Cloud request failed with error: %A (Message = %A)" resp.data.Code resp.data.Message
    }

(* 2. Os info *)
let printOS _ = 
    async {
        let! oss = OSManagement.GetAllAsync()
        match oss with
        | Success resp ->
            printfn "Cloud supported OS families:"
            for os in resp.data do
                printfn "\tVersion = %A; (label = %A)" os.Version os.Label
                printfn "\t\tActive = %A; Default = %A" os.IsActive os.IsDefault
                printfn "\t\tFamily = %A (label = %A)" os.Family os.FamilyLabel
        | Fail resp ->
            printfn "Cloud request failed with error: %A (Message = %A)" resp.data.Code resp.data.Message
        }

let printOSFamilies _ =
    async {
        let! osFamilies = OSManagement.GetAllFamiliesAsync()
        match osFamilies with
        | Success resp ->
            printfn "Cloud supported OS families:"
            for osFamily in resp.data do
                printfn "Family %A (Label = %A)" osFamily.Name osFamily.Label
                for os in osFamily.OSList do
                    printfn "\t\tOS = %A (Active = %A) (Default = %A)" os.Label os.IsActive os.IsDefault
                    printfn "\t\t\tVersion = %A" os.Version
                    printfn "\t\t\tFamily = %A (label = %A)" os.Family os.FamilyLabel
        | Fail resp ->
            printfn "Cloud request failed with error: %A (Message = %A)" resp.data.Code resp.data.Message
    }

(* 3. Affinity groups *)
(* 3.1 list groups *)

let listGroups _ = 
    async {
        let! groups = AffinityGroupAndLocationManagement.GetAllAsync()
        match groups with
        | Success resp when resp.data.Count > 0 ->
            printfn "Your groups:"
            for group in resp.data do 
                printfn "\tLabel = %A (name = %A)" group.Label group.Name
                printfn "\tLocation=%A" group.Location
                printfn "\tDesc=%A" group.Desc
        | Success resp ->
            printfn "0 groups discovered"
        | Fail resp ->
            printfn "Cloud request failed with error: %A (Message = %A)" resp.data.Code resp.data.Message
    }

(* 3.2 create group *)
let createGroup =
    function 
    | name::label::desc::loc::tail -> async {
        let creation = AffinityGroupCreation(Name=name, Label=label, Desc=desc, Location=loc)
        let! created = AffinityGroupAndLocationManagement.CreateAsync(creation)
        match created with
        | Success _ -> 
            printfn "Group was created successfuly"
        | Fail resp ->
            printfn "Cloud request failed with error: %A (Message = %A)" resp.data.Code resp.data.Message
        }
    | _ -> async{
        printfn "Could not create affinity group. Some parameters are missed"
        }
(* 3.3 delete group *)
let deleteGroup = function
    | name::_ -> async {
        let! deleted = AffinityGroupAndLocationManagement.DeleteAsync name
        match deleted with
        | Success _ -> 
            printfn "Group was deleted successfuly" 
        | Fail resp ->
            printfn "Cloud request failed with error: %A (Message = %A)" resp.data.Code resp.data.Message
        }
    | _ -> async {
        printfn "Could not create affinity group. Some parameters are missed"
        }
(* 3.4 get group *)
let getGroup = function
    | name::_ -> async {
        let! group = AffinityGroupAndLocationManagement.GetAsync name
        match group with
        | Success group ->
            printfn "\tGroup %A:" name
            printfn "\tLabel = %A" group.data.Label
            printfn "\tLocation = %A" group.data.Location
            printfn "Desc = %A" group.data.Desc
            if (group.data.Services.Count > 0 ) then
                printfn "\tServices:" 
                for service in group.data.Services do 
                    printfn "\t%A (url = %A)" service.ServiceName service.Url
            else
                printfn "\t0 services"
        | Fail resp ->
            printfn "Cloud request failed with error: %A (Message = %A)" resp.data.Code resp.data.Message
        }
    | _ -> async {
        printfn "Could not create affinity group. Some parameters are missed"
        }
(* 3.5 update group *)
let updateGroup = function
    | name::label::desc::_ -> async {
        let update = AffinityGroupUpdate(label, desc)
        let! group = AffinityGroupAndLocationManagement.UpdateAsync(name, update)
        match group with
        | Success _ ->
            printfn "Group was updated successfuly"
        | Fail resp ->
            printfn "Cloud request failed with error: %A (Message = %A)" resp.data.Code resp.data.Message
        }
    | _ -> async {
        printfn "Could not create affinity group. Some parameters are missed"
        }
(* 4. Hosted service *)
(* 4.1 list *)
let listServices _ = async {
    let! services = HostedServiceManagement.GetAllAsync()
    match services with
    | Success resp when resp.data.Count > 0 ->
        printfn "Your services:"
        for service in resp.data do
            printfn "\tName = %A; \n\tUrl = %A" service.ServiceName service.Url
            (*
            printfn "\t\tProperties:"
            printfn "\tLabel: %A" service.Properties.Label
            printfn "\tDesc: %A" service.Properties.Desc
            if (String.IsNullOrEmpty(service.Properties.AffinityGroup)) then
                printfn "\tLocation: %A" service.Properties.Location
            else
                printfn "\tAffinity group: %A" service.Properties.AffinityGroup
            if (service.Deployments.Count > 0) then
                printfn "\t\tDeployments: "
                for depl in service.Deployments do
                    printfn "\tName = %A (label = %A) sdk=%A" depl.Name depl.Label depl.SdkVersion
                    printfn "\tIs locked = %A, private Id = %A" depl.Locked depl.PrivateId
                    printfn "\tSlot = %A" (depl.Slot.ToString())
                    printfn "\tStatus = %A" (depl.Status.ToString())
                    printfn "\tUrl = %A" depl.Url
            else 
                printfn "\t\t0 deployment founded"
            *)
    | Success resp -> printfn "0 services founded"
    | Fail resp ->
        printfn "Cloud request failed with error: %A (Message = %A)" resp.data.Code resp.data.Message
    }

(* 4.2 create H service *)
let createService = function 
    | name::label::desc::affinity::tail -> async {
        let creation = 
            HostedServiceCreationWithAffinityGroup(name, label, desc, affinity)
        let! created = HostedServiceManagement.CreateWithAffinityGroupAsync(creation)
        match created with
        | Success _ -> 
            printfn "Hosted service created successfuly"
        | Fail resp ->
            printfn "Cloud request failed with error: %A (Message = %A)" resp.data.Code resp.data.Message
        }
    | _ -> async {
        printfn "Could not create hosted service. Some parameters are missed."
        }

(* 4.3 delete service *)
let deleteService = function 
    | name::_ -> async {
        let! deleted = HostedServiceManagement.DeleteAsync(name)
        match deleted with
        | Success _ -> 
            printfn "Hosted service deleted successfuly"
        | Fail resp ->
            printfn "Cloud request failed with error: %A (Message = %A)" resp.data.Code resp.data.Message
        }
    | _ -> async {
        printfn "Could not delete hosted service. Some parameters are missed."
        }

(* 4.4 get service *)
let getService = function 
    | name::_ -> async {
        let! created = HostedServiceManagement.GetAsync(name, true)
        match created with
        | Success resp  -> 
            printfn "Hosted service %A" name
            printfn "Url %A" resp.data.Url
            printfn "\t\tProperties:"
            printfn "\tLabel: %A" resp.data.Properties.Label
            printfn "\tDesc: %A" resp.data.Properties.Desc
            if (String.IsNullOrEmpty(resp.data.Properties.AffinityGroup)) then
                printfn "\tLocation: %A" resp.data.Properties.Location
            else
                printfn "\tAffinity group: %A" resp.data.Properties.AffinityGroup
            if (resp.data.Deployments.Count > 0) then
                printfn "\t\tDeployments: "
                for depl in resp.data.Deployments do
                    printfn "\tName = %A (label = %A) sdk=%A" depl.Name depl.Label depl.SdkVersion
                    printfn "\tIs locked = %A, private Id = %A" depl.Locked depl.PrivateId
                    printfn "\tSlot = %A" (depl.Slot.ToString())
                    printfn "\tStatus = %A" (depl.Status.ToString())
                    printfn "\tUrl = %A" depl.Url
            else 
                printfn "\t\t0 deployment founded"
        | Fail resp ->
            printfn "Cloud request failed with error: %A (Message = %A)" resp.data.Code resp.data.Message
        }
    | _ -> async {
        printfn "Could not get hosted service. Some parameters are missed."
        }

(* 4.5 update H service *)
let updateService = function 
    | name::label::desc::tail -> async {
        let update = HostedServiceUpdate(label, desc)
        let! created = HostedServiceManagement.UpdateAsync(name, update)
        match created with
        | Success _ -> 
            printfn "Hosted service updated successfuly"
        | Fail resp ->
            printfn "Cloud request failed with error: %A (Message = %A)" resp.data.Code resp.data.Message
        }
    | _ -> async {
        printfn "Could not update hosted service. Some parameters are missed."
        }


//createGroup ()
(*  5. Certificates (need before deployment) *)
let listCerts = function
    | name::_ -> async {
        let! certs = CertificatesManagement.GetAllAsync(name)
        match certs with
        | Success resp when resp.data.Count > 0 ->
            printfn "Certificates: "
            for cert in resp.data do
                printfn "\tUrl = %A" cert.Url
                printfn "\tThumbprintf = %A (by %A)" cert.Thumbprint cert.ThumbprintAlgorithm
                printfn "\tData = %A" cert.Data
        | Success resp ->
            printfn "0 certs were found"
        | Fail resp ->
            printfn "Cloud request failed with error: %A (Message = %A)" resp.data.Code resp.data.Message
        }
    | _ -> async {
        printfn "Could not upload cert. Some parameters are missed."
        }
(* 5.1. create cert *)
let addCert = function
    | hs::path::pasw::tail -> async {
        let cert = 
            HostTracker.Azure.Management.Certificate.LoadFromFile(path, pasw)
        let! created = CertificatesManagement.CreateAsync(hs, cert)
        match created with
        | Success _ ->
            printfn "Certificate uploaded successfuly"
        | Fail resp ->
            printfn "Cloud request failed with error: %A (Message = %A)" resp.data.Code resp.data.Message
        }
    | _ -> async {
        printfn "Could not upload cert. Some parameters are missed."
        }

(* 5.2. delete cert *)
let delCert = function
    | hs::thumbprint::_ -> async {
        let! deleted = CertificatesManagement.DeleteAsync(hs, "sha1", thumbprint)
        match deleted with
        | Success _ ->
            printfn "Certificate deleted successfuly"
        | Fail resp ->
            printfn "Cloud request failed with error: %A (Message = %A)" resp.data.Code resp.data.Message
        }
    | _ -> async {
        printfn "Could not delete cert. Some parameters are missed."
        }

(* 5.3. get cert *)
let getCert = function
    | hs::thumbprint::_ -> async {
        let! cert = CertificatesManagement.GetAsync(hs, "sha1", thumbprint)
        match cert with
        | Success resp ->
            printfn "\tCertificate sha1-%A" thumbprint
            printfn "\tUrl: %A" resp.data.Url
            printfn "\tData: %A" resp.data.Data
        | Fail resp ->
            printfn "Cloud request failed with error: %A (Message = %A)" resp.data.Code resp.data.Message
        }
    | _ -> async {
        printfn "Could not find cert. Some parameters are missed."
        }

(* 6. Deployment *)
//"http://htdeployment.blob.core.windows.net/root/HT.Azure.cspkg"
//@"D:\HostTracker\trunk\HT.Azure\bin\Debug\app.publish\ServiceConfiguration.Cloud.cscfg"
let createDeployment = function
    | sn::name::label::url::configPath::tail -> async {
        let creation = 
            DeploymentCreation(true, true, PackageUrl = url,
                Name = name,
                Label = label,
                Config = ServiceConfiguration.LoadFromFile(configPath))
        return! DeploymentManagement.CreateAsLongRunningAsync(sn, Staging, creation, OperationResultHandlers.ConsoleTracers)
        }
    | _ -> async {
        printfn "Could not create deployment. Some parameters are missed."
        }

//"HostTracker.AzureWorker"
//@"D:\HostTracker\trunk\HT.Azure\bin\Debug\app.publish\ServiceConfiguration.Cloud.cscfg"
let changeWorkerInstanceCount = 
    let c = ref 0 
    function
    | sn::configPath::roleName::count::tail when Int32.TryParse(count, c) ->
        async {
            let config = ServiceConfiguration.LoadFromFile(configPath)
            config.Roles.Find(fun role -> role.Name=roleName).InstanceConfig.Count <- !c
            let configChange = ConfigurationChanging(true, Auto, Config = config)
            return! DeploymentManagement.ChangeConfigurationAsLongRunningAsync(sn, Staging, configChange, OperationResultHandlers.ConsoleTracers)
        }
    | _ -> async {
        printfn "Could not change deployment config. Some parameters are missed or wrong."
        }
(* upgrade deployment *)
//@"D:\HostTracker\trunk\HT.Azure\bin\Debug\app.publish\ServiceConfiguration.Cloud.cscfg"
//@"http://htdeployment.blob.core.windows.net/root/HT.Azure.cspkg"
let upgrade = function
    | sn::label::url::configPath::tail ->
        async {
            let glob = 
                DeploymentGlobalUpgarde(Auto, true, Config = ServiceConfiguration.LoadFromFile(configPath),
                    PackageUrl = url,
                    Label = label)
            return! DeploymentManagement.UpgradeAllAsLongRunningAsync(sn, Staging, glob, OperationResultHandlers.ConsoleTracers)
        }
    | _ -> async {
        printfn "Could not upgrade deployment. Some parameters are missed."
        }

type internal Command = 
    NewCloudCommand of Async<unit> * AsyncReplyChannel<unit> //| Stop of AsyncReplyChannel<unit>
    | NewCommand of string*list<string>*AsyncReplyChannel<bool>
with
    static member Commands = 
        Map [
            "get-subscription", printMySubscription
            "get-os", printOS
            "get-os-family", printOSFamilies
            "get-groups", listGroups
            "get-group", getGroup
            "create-group", createGroup
            "delete-group", deleteGroup
            "update-group", updateGroup
            "get-services", listServices
            "get-service", getService
            "delete-service", deleteService
            "create-service", createService
            "update-service", updateService
            "get-certs", listCerts
            "add-cert", addCert
            "delete-cert", delCert
            "get-cert", getCert
            "create-depl", createDeployment
            "change-inst-count", changeWorkerInstanceCount
            "upgrade-depl", upgrade
            "help", fun _ -> async { for (command: System.Collections.Generic.KeyValuePair<string, _>) in Command.Commands do printfn "%A" command.Key } 
        ]

let private createConsoleOperator() = 
    (*standard mailbox*)
    let operator =
        MailboxProcessor.Start(fun inbox ->
            let rec loop () = async {
                let! msg = inbox.Receive()
                match msg with 
                | NewCloudCommand (command, channel) ->
                    async {
                        try do! command with e -> printfn "Error %A" e.Message
                        channel.Reply ()
                    } |> Async.Start
                | NewCommand (name, args, channel) when name = "exit" -> channel.Reply true
                | NewCommand (name, args, channel) ->
                    match Map.tryFind name Command.Commands with
                    | None ->
                        printfn "Specified command was not found. Type 'help' for more info."
                        channel.Reply false
                    | Some cloudOp ->
                        async {
                            try do! cloudOp args with e -> printfn "Error %A" e.Message                            
                            channel.Reply false
                        } |> Async.Start
                return! loop ()
            }
            loop ())
    function 
    | command::args -> operator.PostAndReply (fun channel -> NewCommand(command, args, channel))
    | _ -> printfn "No command was specified"; false
(* TODO path could not contain white spaces - fix it *)
[<EntryPoint>]
let main _ = 
    printfn "This is a test util (for testing cloud commands)."
    let lineProcessor = createConsoleOperator()
    lineProcessor ["help"] |> ignore
    let rec loop () = 
        let line = Console.ReadLine() 
        line.Split([|'"'|], StringSplitOptions.RemoveEmptyEntries) |> Array.fold(fun (num, acc) item ->
            if (num % 2 = 0) then
                (num+1, acc@(item.Split([|' '|], StringSplitOptions.RemoveEmptyEntries) |> Array.toList))
            else 
                (num+1, acc@[item])
            ) (0, [])
        |> snd |> lineProcessor |> function | true -> () | false -> loop ()
    loop ()
    printfn "End"
    0